#include "loopsys.h"

#define LOOPSYS_TASK_MODE_NORMAL  0
#define LOOPSYS_TASK_MODE_TIMER   1
#define LOOPSYS_TASK_MODE_AUTORUN 2

static idle_hook_t g_idle_hook = 0;
static loop_task_t g_task[LOOPSYS_TASK_NUM];

#ifdef LOOPSYS_ENABLE_SYSTIME
static uint32_t g_systick = 0;

void loopsys_tick_increase(void)
{
    g_systick++;
}

uint32_t loopsys_ms_to_tick(uint32_t ms)
{
    uint32_t tick;

    tick  = LOOPSYS_TICK_FREQ * (ms / 1000);
    tick += (LOOPSYS_TICK_FREQ * (ms % 1000) + 999) / 1000;

    return tick;
}

uint32_t loopsys_tick_to_ms(uint32_t tick)
{
    uint32_t ms;
    ms  = tick / LOOPSYS_TICK_FREQ * 1000;
    ms += ((tick / LOOPSYS_TICK_FREQ) * 1000 + LOOPSYS_TICK_FREQ - 1) / LOOPSYS_TICK_FREQ;
    return ms;
}

uint32_t loopsys_systime_get_tick(void)
{
    return g_systick;
}

uint32_t loopsys_systime_get_ms(void)
{
    return loopsys_tick_to_ms(g_systick);
}

void loopsys_delay_tick(uint32_t tick)
{
    uint32_t timeout = g_systick;
    
    timeout += tick;
    while(timeout < g_systick)
    {
    }
}

void loopsys_delay_ms(uint32_t ms)
{
    loopsys_delay_tick(loopsys_ms_to_tick(ms));
}
#endif

static inline void p_loopsys_event_send(loop_task_t *task, uint32_t event)
{
    task->events |= event;
}

static inline void p_loopsys_event_clear(loop_task_t *task, uint32_t event)
{
    task->events &= ~event;
}

static void p_loopsys_task_run(void)
{
    uint32_t i;
    uint32_t events;
    loop_task_t *task = 0;

#ifdef LOOPSYS_ENABLE_TASK_MSG
    //Process msg before schedule task.
    for(i=0; i<LOOPSYS_TASK_NUM; i++)
    {
        //Find the first task with msg and then send a event.
        //Tasks with lower priority will be processed next time.
        if(g_task[i].msg_cnt)
        {
            p_loopsys_event_send(&g_task[i], LOOPSYS_EVENT_MSG);
            break;
        }
    }
#endif

    //Find the first task with events(the first has highest priority).
    for(i=0; i<LOOPSYS_TASK_NUM; i++)
    {
        events = g_task[i].events;
        if(events)
        {
            task = &g_task[i];
            break;
        }
    }

    if(task)
    {
        if(task->func)
        {
            task->func(task, events);
            //Clear all system event.
            p_loopsys_event_clear(task, LOOPSYS_EVENT_SYS_ALL);
#ifdef LOOPSYS_ENABLE_EVENT_AUTO_CLEAR
            //Clear all user event.
            //If event is sent in ISR after we got events, then it will not be cleared.
            p_loopsys_event_clear(task, events);
#endif
        }
    }else
    {
#ifdef LOOPSYS_ENABLE_TASK_AUTORUN
        //No task event means this is a new circle.
        //Send event to autorun task.
        //Make sure autorun task can be executed in next circle.
        for(i=0; i<LOOPSYS_TASK_NUM; i++)
        {
            task = &g_task[i];
            if(task->mode == LOOPSYS_TASK_MODE_AUTORUN)
                p_loopsys_event_send(task, LOOPSYS_EVENT_AUTORUN);
        }
#endif
        //Run idle hook.
        if(g_idle_hook)
            g_idle_hook();
    }
}

loop_task_t *loopsys_task_find(uint8_t prio)
{
    if(prio < LOOPSYS_TASK_NUM && g_task[prio].active)
        return &g_task[prio];
    
    return 0;
}

loop_task_t *loopsys_task_add(uint8_t prio, task_func_t func)
{
    loop_task_t *task;
    
    if(prio >= LOOPSYS_TASK_NUM || !func)
        return 0;
    
    task = &g_task[prio];
    
    if(task->active)
    {
        task = 0;
    }else
    {
        task->priority = prio;
        task->active = 1;
        task->mode = LOOPSYS_TASK_MODE_NORMAL;
        task->func = func;
    }
    
    return task;
}

void loopsys_task_del(loop_task_t *task)
{
    LOOPSYS_ASSERT(task);
    
    task->active = 0;
}

#ifdef LOOPSYS_ENABLE_TASK_TIMER
static void p_loopsys_timer_run(void)
{
    uint32_t i;
    uint32_t ts;
    loop_task_t  *task;
    loop_timer_t *timer;

    ts = g_systick;
    for(i=0; i<LOOPSYS_TASK_NUM; i++)
    {
        task  = &g_task[i];
        if(task->mode == LOOPSYS_TASK_MODE_TIMER)
        {
            timer = &task->timer;
            //If timer is active and timeout, send an event to owner task.
            if(timer->on && timer->timeout <= ts)
            {
                p_loopsys_event_send(task, LOOPSYS_EVENT_TIMEOUT);
                //Reload timer. If repeat=0, deactive timer.
                if(timer->repeat == LOOPSYS_TIMER_FOREVER)
                {
                    timer->timeout = ts + timer->period;
                }else if(timer->repeat > 0)
                {
                    timer->timeout = ts + timer->period;
                    timer->repeat--;
                    if(timer->repeat == 0)
                        timer->on = 0;
                }else
                {
                    //never come here
                }
            }
        }
    }
}

void loopsys_task_timer_enable(loop_task_t *task, uint32_t repeat, uint32_t period)
{
    LOOPSYS_ASSERT(task);
    
    if(task->active && task->mode == LOOPSYS_TASK_MODE_NORMAL)
    {
        task->mode = LOOPSYS_TASK_MODE_TIMER;
        task->timer.on     = 0;
        task->timer.period = loopsys_ms_to_tick(period);
        task->timer.repeat = repeat;
    }
}

void loopsys_task_timer_disable(loop_task_t *task)
{
    LOOPSYS_ASSERT(task);
    
    if(task->active && task->mode == LOOPSYS_TASK_MODE_TIMER)
    {
        task->mode = LOOPSYS_TASK_MODE_NORMAL;
        task->timer.on     = 0;
        task->timer.period = 0;
        task->timer.repeat = 0;
    }
}

void loopsys_task_timer_start(loop_task_t *task)
{
    LOOPSYS_ASSERT(task);
    
    if(task->active && task->mode == LOOPSYS_TASK_MODE_TIMER)
    {
        task->timer.timeout = g_systick + task->timer.period;
        task->timer.on = 1;
    }
}

void loopsys_task_timer_stop(loop_task_t *task)
{
    LOOPSYS_ASSERT(task);
    
    if(task->active && task->mode == LOOPSYS_TASK_MODE_TIMER)
    {
        task->timer.on = 0;
    }
}

#endif

#ifdef LOOPSYS_ENABLE_TASK_AUTORUN
void loopsys_task_autorun_enable(loop_task_t *task)
{
    LOOPSYS_ASSERT(task);
    
    if(task->active && task->mode == LOOPSYS_TASK_MODE_NORMAL)
    {
        task->mode = LOOPSYS_TASK_MODE_AUTORUN;
    }
}

void loopsys_task_autorun_disable(loop_task_t *task)
{
    LOOPSYS_ASSERT(task);
    
    if(task->active && task->mode == LOOPSYS_TASK_MODE_AUTORUN)
    {
        task->mode = LOOPSYS_TASK_MODE_NORMAL;
    }
}
#endif


#ifdef LOOPSYS_ENABLE_TASK_MSG
void loopsys_msg_publish(loop_task_t *pub, uint8_t id, void *data)
{
    int32_t i;
    uint32_t msg_flag = 1 << id;
    loop_task_t  *task;
    
    LOOPSYS_ASSERT(pub);
    LOOPSYS_ASSERT(id < LOOPSYS_MSG_NUM);

    for(i=0; i<LOOPSYS_TASK_NUM; i++)
    {
        task = &g_task[i];
        //Check if this task has subscribed this msg.
        if(task->msg_sub & msg_flag)
        {
            //If msg buffer is full, drop this msg.
            if(task->msg_cnt < LOOPSYS_MSG_BUF_SIZE)
            {
                task->msg[task->msg_cnt].id        = id;
                task->msg[task->msg_cnt].data      = data;
                task->msg[task->msg_cnt].publisher = pub;
                task->msg_cnt++;
            }
        }
    }
}

void loopsys_msg_subscribe(loop_task_t *sub, uint8_t id)
{
    LOOPSYS_ASSERT(sub);
    LOOPSYS_ASSERT(id < LOOPSYS_MSG_NUM);
    
    sub->msg_sub |= (1 << id);
}

void loopsys_msg_unsubscribe(loop_task_t *sub, uint8_t id)
{
    LOOPSYS_ASSERT(sub);
    LOOPSYS_ASSERT(id < LOOPSYS_MSG_NUM);
    
    sub->msg_sub &= ~(1 << id);
}

int32_t loopsys_msg_get(loop_task_t *sub, loop_msg_t **msg)
{
    int32_t cnt;
    LOOPSYS_ASSERT(sub);
    
    if(sub->msg_cnt)
        *msg = sub->msg;
    else
        *msg = 0;
 
    //Clear msg buffer.
    cnt = sub->msg_cnt;
	sub->msg_cnt = 0;
    return cnt;
}
#endif

void loopsys_event_send(loop_task_t *task, uint32_t event)
{
    LOOPSYS_ASSERT(task);
    
    p_loopsys_event_send(task, event & LOOPSYS_EVENT_MASK);
}

void loopsys_event_clear(loop_task_t *task, uint32_t event)
{
    LOOPSYS_ASSERT(task);
    
    p_loopsys_event_clear(task, event & LOOPSYS_EVENT_MASK);
}

void loopsys_event_clear_all(loop_task_t *task)
{
    LOOPSYS_ASSERT(task);
    
    task->events = 0;
}
void loopsys_set_idle_hook(idle_hook_t hook)
{
    g_idle_hook = hook;
}

void loopsys_run(void)
{
#ifdef LOOPSYS_ENABLE_TASK_TIMER
    p_loopsys_timer_run();
#endif
    p_loopsys_task_run();
}
